package com.gitee.easyopen;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.auth0.jwt.interfaces.Claim;
import com.gitee.easyopen.bean.ApiDefinition;
import com.gitee.easyopen.bean.Consts;
import com.gitee.easyopen.bean.DefinitionHolder;
import com.gitee.easyopen.interceptor.ApiInterceptor;
import com.gitee.easyopen.message.Errors;
import com.gitee.easyopen.util.CopyUtil;
import com.gitee.easyopen.util.ReflectionUtil;

/**
 * 处理客户端请求分发
 * 
 * @author tanghc
 *
 */
public class ApiInvoker implements Invoker {

    private static final Logger logger = LoggerFactory.getLogger(ApiInvoker.class);
    
    private static final ApiInterceptor[] EMPTY_INTERCEPTOR_ARRAY = {};
    
    private ApiConfig apiConfig; // 配置项
    
    public ApiInvoker() {
        super();
    }

    public ApiInvoker(ApiConfig apiConfig) {
        super();
        this.apiConfig = apiConfig;
    }
    

    @Override
    public Object invoke(HttpServletRequest request, HttpServletResponse response) throws Throwable {
        ApiContext.setRequest(request);
        try {
            ApiParam param = this.apiConfig.getParamParser().parse(request); // 解析参数
            ApiContext.setApiParam(param);
            this.initJwtInfo(request, param);
            return this.doInvoke(param,request,response);
        } catch (Throwable e) {
            if(e instanceof InvocationTargetException) {
                e = ((InvocationTargetException)e).getTargetException();
            }
            logger.error(e.getMessage(), e);
            throw e;
        }
    }
    
    private void initJwtInfo(HttpServletRequest request,ApiParam param) {
        Map<String, Claim> data = null;
        String jwt = this.getHeader(request, Consts.AUTHORIZATION);
        if(jwt != null && jwt.startsWith(Consts.PREFIX_BEARER)) {
            jwt = jwt.replace(Consts.PREFIX_BEARER, "");
            data = this.apiConfig.getJwtService().verfiyJWT(jwt);
        }
        ApiContext.setJwtData(data);
    }
    
    protected String getHeader(HttpServletRequest request, String key) {
        String value = request.getHeader(key);
        if(value == null) {
            return null;
        }
        if(ApiContext.isEncryptMode()) {
            value = ApiContext.decryptAES(value);
        }
        return value;
    }
    
    
    /**
     * 调用具体的业务方法，返回业务结果
     * @param param
     * @param request
     * @param response
     * @return 返回最终结果
     * @throws Throwable
     */
    protected Object doInvoke(ApiParam param,HttpServletRequest request, HttpServletResponse response) throws Throwable {
        Object methodArgu = null; // 方法参数
        Object invokeResult = null; // 返回结果
        
        Validator validator = this.apiConfig.getValidator();
        
        ApiDefinition definition = DefinitionHolder.getByParam(param);
        if (definition == null) {
            throw Errors.NO_API.getException(param.fatchName(), param.fatchVersion());
        }
        param.setIgnoreSign(definition.isIgnoreSign());
        param.setIgnoreValidate(definition.isIgnoreValidate());
        // 验证操作，这里有负责验证签名参数
        validator.validate(param);

        String busiJsonData = param.fatchData(); // 业务参数json格式
        if(!StringUtils.hasText(busiJsonData)) {
            busiJsonData = URLEncoder.encode("{}", Consts.UTF8);
        }
        busiJsonData = URLDecoder.decode(busiJsonData, Consts.UTF8);
        // 业务参数Class
        Class<?> arguClass = definition.getMethodArguClass();
        
        int interceptorIndex = 0;
        try {
            if (arguClass != null) { // 将参数绑定到业务方法参数上，业务方法参数可以定义的类型：JSONObject,Map<String,Object>,String,业务参数类
                if(arguClass == JSONObject.class) {
                    methodArgu = JSON.parseObject(busiJsonData);
                } else if(arguClass == Map.class) {
                    methodArgu = new HashMap<String,Object>(JSON.parseObject(busiJsonData));
                } else if(arguClass == String.class) {
                    methodArgu = busiJsonData;
                } else {
                    methodArgu = JSON.parseObject(busiJsonData, arguClass);
                }
                this.bindUploadFile(methodArgu);
            }
            // 拦截器
            ApiInterceptor[] interceptors = this.apiConfig.getInterceptors();
            if(interceptors == null) {
                interceptors = EMPTY_INTERCEPTOR_ARRAY;
            }
        
            //1. 调用preHandle
            for (int i = 0; i < interceptors.length; i++) {
                ApiInterceptor interceptor = interceptors[i];  
                if (interceptor.match(definition) && !interceptor.preHandle(request, response, definition.getHandler(),methodArgu)) {  
                    //1.1、失败时触发afterCompletion的调用  
                    triggerAfterCompletion(definition, interceptorIndex, request, response, methodArgu, null,null);  
                    return null;  
                }  
                interceptorIndex = i;//1.2、记录当前预处理成功的索引  
            }  
            /* *** 调用业务方法,被@Api标记的方法 ***/
            
            // 验证业务参数JSR-303
            validator.validateBusiParam(methodArgu);
            
            if (methodArgu == null) {
                invokeResult = definition.getMethod().invoke(definition.getHandler());
            } else {
                invokeResult = definition.getMethod().invoke(definition.getHandler(), methodArgu);
            }
            /* *** 调用业务方法end ***/
            
            //3、调用postHandle,业务方法调用后处理（逆序）  
            for (int i = interceptors.length - 1; i >= 0; i--) {  
                ApiInterceptor interceptor = interceptors[i];  
                if(interceptor.match(definition)) {
                    interceptor.postHandle(request, response, definition.getHandler(), methodArgu, invokeResult);  
                }
            }  
            
            if(invokeResult == null) {
                invokeResult = new EmptyObject();
            }
            
            Object retObj = invokeResult; // 最终返回的对象
            
            if(definition.isWrapResult()) { // 对返回结果包装
                retObj = this.apiConfig.getResultCreator().createResult(invokeResult);
            }
            //4、触发整个请求处理完毕回调方法afterCompletion  
            triggerAfterCompletion(definition, interceptorIndex, request, response, methodArgu, retObj, null);
            
            return retObj;
        } catch (Exception e) {
            this.triggerAfterCompletion(definition, interceptorIndex, request, response, methodArgu, invokeResult, e);
            throw e;
        }
    }
    
    // triggerAfterCompletion方法  
    private void triggerAfterCompletion (ApiDefinition definition,int interceptorIndex,  
                HttpServletRequest request, HttpServletResponse response, Object argu,Object result, Exception e) throws Exception {  
        // 5、触发整个请求处理完毕回调方法afterCompletion （逆序从1.2中的预处理成功的索引处的拦截器执行）  
        ApiInterceptor[] interceptors = this.apiConfig.getInterceptors();  
        
        if(interceptors != null && interceptors.length > 0) {
            for (int i = interceptorIndex; i >= 0; i--) {  
                ApiInterceptor interceptor = interceptors[i];
                if(interceptor.match(definition)) {
                    interceptor.afterCompletion(request, response, definition.getHandler(),argu,result, e);
                }
            }  
        }
    }
    
    /**
     * 绑定上传文件到参数类当中
     * @param methodArgu
     */
    protected void bindUploadFile(Object methodArgu) {
        if(methodArgu != null) {
            UploadContext uploadContext = ApiContext.getUploadContext();
            if(uploadContext != null) {
                List<MultipartFile> files = uploadContext.getAllFile();
                if(files != null && files.size() > 0) {
                    Map<String,Object> filesMap = new HashMap<>(files.size());
                    
                    for (MultipartFile file : files) {
                        filesMap.put(file.getName(), file);
                    }
                    
                    CopyUtil.copyProperties(filesMap, methodArgu);
                    
                    Field field = ReflectionUtil.getListFieldWithGeneric(methodArgu, MultipartFile.class);
                    if(field != null) {
                        ReflectionUtil.invokeFieldValue(methodArgu, field.getName(), files);
                    }
                }
            }
        }
    }
    
    
    private static class EmptyObject implements Serializable {
        private static final long serialVersionUID = 1713263598232463135L;
    }

    @Override
    public ApiConfig getApiConfig() {
        return apiConfig;
    }

    @Override
    public void setApiConfig(ApiConfig apiConfig) {
        this.apiConfig = apiConfig;
    }

}
